"""代码生成命令模块"""

import argparse
import json
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List
from jinja2 import Environment

from src.core.command import Command
from src.base.registry import bera
from src.base.sql_file_utils import SQLFileUtils
from src.base.project_config import ProjectConfig
from src.core.common.template_engine import generate
from src.core.common.derived import derive
from src.commands.gen_meta import GenMetaCommand

class GenCodeCommand(Command):
    """代码生成命令"""

    # 类级别的会话时间戳，确保同一会话中所有实例使用相同时间戳
    _session_timestamp = None

    def __init__(self):
        super().__init__()
        self.name = "gen-code"
        self.group = "代码生成"
        self.help = "根据模板生成代码"
        self.description = """
        根据模板生成代码。该命令会:
        1. 验证模板目录和配置
        2. 确保元数据文件存在,如不存在则自动生成
        3. 根据模板生成代码文件
        """

    @classmethod
    def get_session_timestamp(cls) -> str:
        """获取会话级别的统一时间戳

        Returns:
            str: 格式为YYYYMMDDHHMMSS的时间戳字符串
        """
        if cls._session_timestamp is None:
            cls._session_timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            bera.logger.debug(f"生成新的会话时间戳: {cls._session_timestamp}")
        return cls._session_timestamp

    @classmethod
    def reset_session_timestamp(cls) -> None:
        """重置会话时间戳，用于开始新的生成会话"""
        old_timestamp = cls._session_timestamp
        cls._session_timestamp = None
        if old_timestamp:
            bera.logger.debug(f"重置会话时间戳: {old_timestamp} -> None")

    def add_arguments(self, parser: argparse.ArgumentParser) -> None:
        """添加命令参数"""
        # 添加通用参数(filename/filepath/directory)
        SQLFileUtils.add_arguments(parser)

        # 添加模板参数
        parser.add_argument('-t', '--template', required=False, help='模板名称，如果不指定则使用项目根目录下的templates目录')
        # 添加工作目录参数
        parser.add_argument('-w', '--workspace_path', help='工作目录路径')

    def execute(self, args: argparse.Namespace) -> None:
        """执行命令"""
        try:
            bera.logger.info("开始执行gen-code命令")

            # 初始化项目配置
            ProjectConfig.init(args.project_dir)

            # 重置并设置SQL搜索路径
            SQLFileUtils.reset_search_paths()
            project_sql_dir = ProjectConfig.get_sql_dir()
            SQLFileUtils.add_search_path(str(project_sql_dir))

            # 使用统一的输入模式处理
            SQLFileUtils.execute_file_processing(args, self._process_sql_file)

            bera.logger.info("gen-code命令执行完成")
        except Exception as e:
            if args.verbose:
                import traceback
                traceback.print_exc()
            SQLFileUtils.handle_file_error(e, Path(args.filepath) if args.filepath else None)

    def _validate_template_dir(self, template_dir: Path, verbose: bool = False) -> Dict[str, Any]:
        """验证模板目录并读取配置

        Args:
            template_dir: 模板目录路径
            verbose: 是否显示详细日志

        Returns:
            Dict[str, Any]: 模板配置

        Raises:
            FileNotFoundError: 当模板目录或配置文件不存在时
            ValueError: 当模板配置无效时
        """
        if not template_dir.exists():
            raise FileNotFoundError(f"模板目录不存在: {template_dir}")

        template_json = template_dir / "template.json"
        if not template_json.exists():
            raise FileNotFoundError(f"模板配置文件不存在: {template_json}")

        try:
            template_config = SQLFileUtils.read_json_file(template_json, verbose)
            if not isinstance(template_config, dict) or "files" not in template_config:
                raise ValueError("无效的模板配置文件: 缺少files配置")
            return template_config
        except json.JSONDecodeError as e:
            raise ValueError(f"模板配置文件格式错误: {str(e)}")

    def _get_template_dir(self, template_name: str = None) -> Path:
        """获取模板目录

        支持三种模式:
        1. 相对路径: 从当前工作目录查找模板
        2. 绝对路径: 直接使用完整路径
        3. 默认路径: 使用项目根目录下的templates目录

        Args:
            template_name: 模板名称，如果为None则使用默认模板目录

        Returns:
            Path: 模板目录路径
        """
        # 如果没有指定模板名称，使用项目根目录下的templates目录
        if template_name is None:
            default_template_dir = ProjectConfig.get_project_dir() / "templates"
            if not default_template_dir.exists():
                raise FileNotFoundError(f"默认模板目录不存在: {default_template_dir}")
            return default_template_dir

        template_path = Path(template_name)

        # 如果是绝对路径，直接使用
        if template_path.is_absolute():
            return template_path

        # 如果是相对路径，从当前工作目录查找
        template_path = Path.cwd() / template_path
        if template_path.exists():
            return template_path

        # 如果在当前工作目录下找不到，尝试从项目模板目录查找
        return ProjectConfig.get_template_dir() / template_name

    def _get_base_dir(self, args: argparse.Namespace) -> Path:
        """获取基础目录路径"""
        if hasattr(args, 'workspace_path') and args.workspace_path:
            output_dir = Path(args.workspace_path)
            # 如果是相对路径,转换为绝对路径
            if not output_dir.is_absolute():
                output_dir = Path.cwd() / output_dir
            return output_dir

        # 使用项目配置中的输出目录
        output_dir = ProjectConfig.get_output_dir()

        # 检查 output-dir 是否为绝对路径
        config = ProjectConfig.get_config()
        output_dir_config = config.get('output-dir', '')

        # 如果配置的 output-dir 是绝对路径，直接使用，不添加时间戳
        if Path(output_dir_config).is_absolute():
            return output_dir

        # 否则添加统一的会话时间戳后缀
        session_timestamp = self.get_session_timestamp()
        return output_dir / f'generated_{session_timestamp}'

    def _get_meta_file_path(self, table_name: str) -> Path:
        """获取元数据文件路径"""
        return SQLFileUtils.get_output_table_dir(table_name) / 'meta.auto.json'

    def _create_gen_meta_args(self, table_name: str, args: argparse.Namespace) -> argparse.Namespace:
        """创建gen-meta命令的参数"""
        return argparse.Namespace(
            filename=None,
            filepath=args.filepath if hasattr(args, 'filepath') else table_name,
            directory=None,
            project_dir=args.project_dir,
            verbose=args.verbose,
            force=False
        )

    def _ensure_meta_file(self, table_name: str, args: argparse.Namespace) -> Path:
        """确保元数据文件存在

        Args:
            table_name: 表名
            args: 命令行参数

        Returns:
            Path: 元数据文件路径

        Raises:
            FileNotFoundError: 当元数据文件生成失败时
        """
        meta_file = self._get_meta_file_path(table_name)
        bera.logger.info(f"元数据文件路径: {meta_file}")

        if not meta_file.exists():
            bera.logger.info(f"元数据文件不存在,自动生成: {meta_file}")
            gen_meta_cmd = GenMetaCommand()
            gen_meta_args = self._create_gen_meta_args(table_name, args)
            bera.logger.debug(f"生成元数据参数: {gen_meta_args}")
            gen_meta_cmd.execute(gen_meta_args)

            if not meta_file.exists():
                raise FileNotFoundError(f"元数据文件生成失败: {meta_file}")

            bera.logger.info("元数据文件生成成功")
            if args.verbose:
                meta_content = SQLFileUtils.read_json_file(meta_file, args.verbose)
                bera.logger.debug(f"元数据内容: {json.dumps(meta_content, indent=2, ensure_ascii=False)}")

        return meta_file

    def _process_sql_file(self, filepath: str, args: argparse.Namespace) -> None:
        """处理单个文件

        Args:
            filepath: SQL文件路径
            args: 命令行参数
        """
        try:
            # 从文件路径中提取表名
            table_name = Path(filepath).stem
            if args.verbose:
                bera.logger.info(f"处理表: {table_name}")

            # 1. 验证模板目录和配置
            template_dir = self._get_template_dir(args.template if hasattr(args, 'template') else None)
            if args.verbose:
                bera.logger.info(f"使用{'默认' if not hasattr(args, 'template') or args.template is None else '指定'}模板目录: {template_dir}")
            template_config = self._validate_template_dir(template_dir, args.verbose)

            # 2. 确保元数据文件存在
            meta_file = self._ensure_meta_file(table_name, args)

            # 3. 创建输出目录
            output_dir = self._get_base_dir(args)
            bera.logger.info(f"[MIAOMA_OUTPUT_DIR] 输出目录: {output_dir}")
            SQLFileUtils.ensure_directory(output_dir)

            # 4. 生成代码
            self._generate_code(template_dir, template_config, meta_file, output_dir)

        except Exception as e:
            bera.logger.error(f"处理表 {table_name} 失败: {str(e)}")
            if args.verbose:
                import traceback
                traceback.print_exc()
            SQLFileUtils.handle_file_error(e, Path(filepath), table_name)

    def _generate_code(
        self,
        template_dir: Path,
        template_config: Dict[str, Any],
        meta_file: Path,
        output_dir: Path
    ) -> None:
        """生成代码"""
        try:
            # 1. 读取meta文件并处理衍生配置
            meta_content = SQLFileUtils.read_json_file(meta_file)
            meta_content = derive(meta_content)

            # 2. 标记项目配置信息到元数据
            meta_content = apply_with_project_config(meta_content)

            # 2. 遍历template.json中的files配置
            for file_config in template_config["files"]:
                try:
                    # 1. 获取模板文件路径
                    template_file = template_dir / file_config["template"]
                    if not template_file.exists():
                        bera.logger.warning(f"模板文件不存在,跳过: {template_file}")
                        continue

                    # 2. 渲染目标路径和文件名
                    env = Environment()
                    target_path = env.from_string(file_config["target_path"]).render(**meta_content)
                    target_name = env.from_string(file_config["target"]).render(**meta_content)
                    target_file = output_dir / target_path / target_name

                    # 3. 确保目标目录存在
                    SQLFileUtils.ensure_directory(target_file.parent)

                    # 4. 删除目标文件(如果存在)
                    if target_file.exists():
                        bera.logger.debug(f"删除已存在的目标文件: {target_file}")
                        target_file.unlink()

                    # 5. 生成代码
                    desc = file_config.get('desc', f'生成文件: {target_name}')
                    bera.logger.debug(desc)
                    generate(
                        template_file_path=str(template_file),
                        target_abs_path=str(target_file),
                        meta_content=meta_content  # 使用已经处理好的meta_content
                    )
                    bera.logger.info(f"成功生成文件: {target_file}")

                except Exception as e:
                    bera.logger.error(f"生成文件失败 {target_file}: {str(e)}")
                    if isinstance(e, (FileNotFoundError, PermissionError)):
                        raise
                    continue

        except Exception as e:
            bera.logger.error(f"生成代码失败: {str(e)}")
            raise


def apply_with_project_config(meta_content: Dict[str, Any]) -> Dict[str, Any]:
    """用项目配置信息丰富元数据

    Args:
        meta_content: 原始元数据内容

    Returns:
        Dict[str, Any]: 丰富后的元数据内容
    """
    try:
        # 获取项目配置中的自动填充字段
        autofilled_fields = ProjectConfig.get_autofilled_fields()

        # 在table.config中设置autofilled信息
        if 'table' not in meta_content:
            meta_content['table'] = {}
        if 'config' not in meta_content['table']:
            meta_content['table']['config'] = {}

        meta_content['table']['config']['autofilled'] = autofilled_fields

        # 为每个字段在columns中添加config.autofilled属性
        if 'columns' in meta_content:
            for field_name, field_info in meta_content['columns'].items():
                # 确保config节点存在
                if 'config' not in field_info:
                    field_info['config'] = {}

                # 设置autofilled属性
                field_info['config']['autofilled'] = ProjectConfig.is_autofilled_field(field_name)

                bera.logger.debug(f"字段 {field_name} 的config配置: autofilled={field_info['config']['autofilled']}")

        bera.logger.debug(f"项目配置标记完成: autofilled_fields={autofilled_fields}")
        return meta_content

    except Exception as e:
        bera.logger.warning(f"标记项目配置信息失败: {str(e)}")
        return meta_content